package kd.bos.openapi.sdk.util;

import com.alibaba.fastjson.JSON;
import kd.bos.openapi.sdk.exception.APIException;
import kd.bos.openapi.sdk.response.result.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Deacription Http请求工具类
 * @author johnz
 * @date 2022年4月6日
 */
@Slf4j
public class HttpClientUtil {
    // 默认连接超时时间
    private static int DEFAULT_CONNECTION_TIMEOUT=Integer.getInteger("httpclient.connectionTimeout",10000);
    // 默认连接读取超时时间
    private static int DEFAULT_READ_TIMEOUT=Integer.getInteger("httpclient.readTimeout",20000);

    private static PoolingHttpClientConnectionManager cm = null;
    static {
        cm = new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(1000);
        cm.setDefaultMaxPerRoute(1000);
    }

    /**
     * 请求post方法
     *
     * @param url     访问URL
     * @param header  请求header参数，map结构
     * @param bodyMap 请求body参数， map结构
     * @return 请求返回
     * @throws APIException 异常
     */
    public static String post(String url, Map<String, String> header, Map<String, Object> bodyMap) throws APIException {
        return post(url, header, bodyMap, true, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_READ_TIMEOUT);
    }

    /**
     * 请求post方法
     *
     * @param url        访问URL
     * @param header     请求header参数，map结构
     * @param bodyObject 请求body参数， 对象
     * @return 请求返回
     * @throws APIException 异常
     */
    public static String post(String url, Map<String, String> header, Object bodyObject) throws APIException {
        return post(url, header, bodyObject, true, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_READ_TIMEOUT);
    }

    /**
     * 请求post方法
     *
     * @param url        访问URL
     * @param header     请求header参数，map结构
     * @param jsonString 请求body参数， json字符串
     * @return 请求返回
     * @throws APIException 异常
     */
    public static String post(String url, Map<String, String> header, String jsonString) throws APIException {
        return post(url, header, jsonString, true);
    }

    /**
     * 请求post方法
     *
     * @param url        访问URL
     * @param header     请求header参数，map结构
     * @param xmlString 请求body参数， xml字符串
     * @return 请求返回
     * @throws APIException 异常
     */
    public static String postByXml(String url, Map<String, String> header, String xmlString) throws APIException {
        return post(url, header, xmlString, false);
    }

    /**
     * 请求post方法
     *
     * @param url        访问URL
     * @param header     请求header参数，map结构
     * @param requestString 请求body参数， 字符串
     * @param isBodyJson 请求body参数是否是json字符串， 如果不是则传xml,如果为false，需要在header中传 Content-Type
     * @return 请求返回
     * @throws APIException 异常
     */
    public static String post(String url, Map<String, String> header, String requestString, boolean isBodyJson) throws APIException {
        return post(url, header, requestString, isBodyJson, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_READ_TIMEOUT);
    }

    /**
     * 请求post方法
     *
     * @param url        访问URL
     * @param header     请求header参数，map结构
     * @param body       请求body参数
     * @param isBodyJson 请求body是否json结构发送
     * @return 请求返回
     * @throws APIException 异常
     */
    public static String post(String url, Map<String, String> header, Map<String, Object> body, boolean isBodyJson) throws APIException {
        return post(url, header, body, isBodyJson, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_READ_TIMEOUT);
    }

    /**
     * 请求post方法
     *
     * @param url  访问URL
     * @param body 请求body参数, 支持Map结构参数
     * @return 请求返回
     * @throws APIException 异常
     */
    public static String post(String url, Map<String, Object> body) throws APIException {
        Map<String, String> header = new HashMap<>();
        return post(url, header, body, true, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_READ_TIMEOUT);
    }

    /**
     * 请求post方法
     *
     * @param url               访问URL
     * @param header            请求header参数，map结构
     * @param body              请求body参数
     * @param isBodyJson        请求body是否json结构发送
     * @param connectionTimeout 连接超时时间
     * @param readTimeout       读取超时时间
     * @return 请求返回
     * @throws APIException 异常
     */
    public static String post(String url, Map<String, String> header, Object body, boolean isBodyJson, int connectionTimeout, int readTimeout) throws APIException {
        String data = "";
        HttpClient client = null;
        HttpPost post = new HttpPost(url);
        URI uri = post.getURI();
        if ("https".equalsIgnoreCase(uri.getScheme())) {
            client = createClient(true,false,true);
        } else {
            client = createClient(true,false,false);
            post.setConfig(getRequestConfig(connectionTimeout, readTimeout));
        }
        try {
            for (Map.Entry<String, String> entry : header.entrySet()) {
                post.setHeader((String) entry.getKey(), (String) entry.getValue());
            }
            if (body != null) {
                HttpEntity entity = null;
                if (isBodyJson) {
                    if (body instanceof String) {
                        entity = new StringEntity((String) body, "UTF-8");
                    } else {
                        entity = new StringEntity(JSON.toJSONString(body), "UTF-8");
                    }
                   // post.setHeader("Content-Type", "application/json;charset=UTF-8");
                    post.setHeader("Content-Type", "application/json");
                } else if (body instanceof String) {
                    entity = new StringEntity((String) body, "UTF-8");
                } else if (body instanceof Map) {
                    Map<String, String> bodyParams = (Map<String, String>) body;
                    List<NameValuePair> paramsList = new ArrayList<>();
                    for (Map.Entry<String, String> entryParams : bodyParams.entrySet()) {
                        paramsList.add(new BasicNameValuePair(entryParams.getKey(), entryParams.getValue()));
                    }
                    entity = new UrlEncodedFormEntity(paramsList, HTTP.UTF_8);
                } else {
                    throw APIException.of(ResultCode.BUSINESS_ERROR_CODE_EXCEPTION, "cannot support body dataType");
                }
                post.setEntity(entity);
            }

            HttpResponse response = client.execute(post);
           // if (response.getStatusLine().getStatusCode() == 200) {
                HttpEntity resEntity = response.getEntity();
                Header resHeader = resEntity.getContentEncoding();
                if (resHeader == null || !"gzip".equalsIgnoreCase(resHeader.getValue()) && !"x-gzip".equalsIgnoreCase(resHeader.getValue())) {
                    data = EntityUtils.toString(resEntity);
                } else {
                    log.error("cannot support gzip or x-gzip header!");
                    throw APIException.of(ResultCode.BUSINESS_FAILURE_EXCEPTION, "cannot support gzip or x-gzip header!");
                }
           // }
        } catch (Exception e) {
            log.error("Exception error", e);
            throw APIException.of(ResultCode.BUSINESS_ERROR_CODE_EXCEPTION, "Exception error"+e.getMessage());
        } finally {
            post.releaseConnection();
        }
        return data;
    }

    /**
     * Get请求方法
     *
     * @param url 请求URL
     * @return 返回结果
     * @throws Exception 异常
     */
    public static String get(String url) throws APIException {
        return get(url, null, null, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_READ_TIMEOUT);
    }

    /**
     * Get请求方法
     *
     * @param url    请求URL
     * @param header 请求header参数，map结构
     * @param params 请求参数，map结构
     * @return 返回结果
     * @throws APIException 异常
     */
    public static String get(String url, Map<String, String> header, Map<String, Object> params) throws APIException {
        return get(url, header, params, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_READ_TIMEOUT);
    }

    /**
     * Get请求方法
     *
     * @param url 请求URL
     * @param header 请求header参数，map结构
     * @param body 请求参数，map结构
     * @param connectionTimeout 连接超时时间
     * @param readTimeout 读取超时时间
     * @return 返回结果
     * @throws APIException 异常
     */
    public static String get(String url, Map<String, String> header, Map<String, Object> body, int connectionTimeout, int readTimeout) throws APIException {
        BufferedReader in = null;
        String content = null;
        String strBody = "";
        try {
            HttpClient client = null;
            if (body != null && !body.isEmpty()) {
                HttpEntity entity = null;
                List<NameValuePair> paramsList = new ArrayList<>();
                for (Map.Entry<String, Object> entryParams : body.entrySet()) {
                    paramsList.add(new BasicNameValuePair(entryParams.getKey(), entryParams.getValue().toString()));
                }
                entity = new UrlEncodedFormEntity(paramsList, HTTP.UTF_8);
                strBody = EntityUtils.toString(entity);
            }

            String destUrl = "";
            if (StringUtils.isEmpty(strBody)) {
                destUrl = url;
            } else {
                destUrl = url + "?" + strBody;
            }
            URI uri = URI.create(destUrl);
            HttpGet request=new HttpGet(uri);
            request.setConfig(getRequestConfig(connectionTimeout, readTimeout));
            if (header != null && !header.isEmpty()) {
                for (Map.Entry<String, String> entry : header.entrySet()) {
                    request.setHeader(entry.getKey(), entry.getValue());
                }
            }

            if ("https".equalsIgnoreCase(uri.getScheme())) {
                client = createClient(true, false, true);
            } else {
                client = createClient(true, false, false);
            }

            HttpResponse response = client.execute(request);
            in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
            StringBuffer stringBuffer = new StringBuffer();
            String line = "";
            String nl = System.getProperty("line.separator");
            while ((line = in.readLine()) != null) {
                stringBuffer.append(line + nl);
            }
            content = stringBuffer.toString();
            return content;
        } catch (Exception e) {
            log.error("IOException error", e);
            throw APIException.of(ResultCode.BUSINESS_FAILURE_EXCEPTION, "IOException error", e);
        } finally {
            IOUtil.closeQuietly(in);
        }
    }

    /**
     * 获取请求配置
     * @param connectionTimeout 连接超时时间
     * @param readTimeout 读取时间
     * @return 配置
     */
    private static RequestConfig getRequestConfig(int connectionTimeout, int readTimeout) {
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(connectionTimeout)
                .setSocketTimeout(readTimeout)
                .setConnectionRequestTimeout(readTimeout)
                .setCookieSpec("compatibility")
                .build();
        return requestConfig;
    }

    private static HttpClient createHttpClient(){
        HttpClient client = HttpClients.createDefault();
        return client;
    }

    /**
     * @param isPooled 是否使用链接池
     * @param isRetry 是否重试请求
     * @param isSchemaHttps 是否https
     */
    public static HttpClient createClient(boolean isPooled, boolean isRetry, boolean isSchemaHttps) {
        HttpClient httpClient = null;
        HttpRequestRetryHandler handler = null;
        if (isRetry) {
            handler = new HttpRequestRetryHandler() {
                @Override
                public boolean retryRequest(IOException arg0, int retryTimes, HttpContext arg2) {
                    if (retryTimes > 2) {
                        return false;
                    }
                    if (arg0 instanceof UnknownHostException || arg0 instanceof ConnectTimeoutException
                            || !(arg0 instanceof SSLException) || arg0 instanceof NoHttpResponseException) {
                        return true;
                    }

                    HttpClientContext clientContext = HttpClientContext.adapt(arg2);
                    HttpRequest request = clientContext.getRequest();
                    boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
                    if (idempotent) {
                        // 若是请求被认为是幂等的，那么就重试。即重复执行不影响程序其余效果的
                        return true;
                    }
                    return false;
                }
            };
        }

        if (isSchemaHttps) {
            try {
                SSLConnectionSocketFactory scsf = new SSLConnectionSocketFactory(
                        SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build(),
                        NoopHostnameVerifier.INSTANCE);
                if (isRetry & handler != null) {
                    if (isPooled) {
                        httpClient = HttpClients.custom().setConnectionManager(cm).setSSLSocketFactory(scsf).setRetryHandler(handler).build();
                    } else {
                        httpClient = HttpClients.custom().setSSLSocketFactory(scsf).setRetryHandler(handler).build();
                    }
                } else {
                    if (isPooled) {
                        httpClient = HttpClients.custom().setConnectionManager(cm).setSSLSocketFactory(scsf).build();
                    } else {
                        httpClient = HttpClients.custom().setSSLSocketFactory(scsf).build();
                    }
                }
                return httpClient;
            } catch (Exception e) {
                log.error("error", e);
                throw APIException.of(ResultCode.BUSINESS_FAILURE_EXCEPTION, "IOException error", e);
            }
        }
        if (isPooled) {
            httpClient = HttpClients.custom().setConnectionManager(cm).setRetryHandler(handler).build();
        } else {
            httpClient = HttpClients.custom().setRetryHandler(handler).build();
        }
        return httpClient;
    }

}
